﻿
package kliment.display.filters.color {

	public dynamic class ColorMatrix extends Array {
		
		private const DELTA_INDEX:Array = [
			0,    0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1,  0.11,
			0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24,
			0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42,
			0.44, 0.46, 0.48, 0.5,  0.53, 0.56, 0.59, 0.62, 0.65, 0.68, 
			0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98,
			1.0,  1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54,
			1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.0,  2.12, 2.25, 
			2.37, 2.50, 2.62, 2.75, 2.87, 3.0,  3.2,  3.4,  3.6,  3.8,
			4.0,  4.3,  4.7,  4.9,  5.0,  5.5,  6.0,  6.5,  6.8,  7.0,
			7.3,  7.5,  7.8,  8.0,  8.4,  8.7,  9.0,  9.4,  9.6,  9.8, 
			10.0
		];
		
		public static const IDENTITY_MATRIX:Array = [
			1, 0, 0, 0, 0,
			0, 1, 0, 0, 0,
			0, 0, 1, 0, 0,
			0, 0, 0, 1, 0
		];
		
		private const LENGTH:Number = IDENTITY_MATRIX.length;
		
		public function ColorMatrix(matrix:Array = null):void {
			copyMatrix(((matrix != null && matrix.length == LENGTH) ? matrix : IDENTITY_MATRIX));
		}

		public function red(value:Number):void {
			value = cleanValue(value, 1)+1;
			if (value == 0 || isNaN(value)) { return; }
			multiplyMatrix([
				value, 0, 0, 0, 0,
				0, 1, 0, 0, 0,
				0, 0, 1, 0, 0,
				0, 0, 0, 1, 0
			]);
		}
		
		public function green(value:Number):void {
			value = cleanValue(value, 1)+1;
			if (value == 0 || isNaN(value)) { return; }
			multiplyMatrix([
				1, 0, 0, 0, 0,
				0, value, 0, 0, 0,
				0, 0, 1, 0, 0,
				0, 0, 0, 1, 0
			]);
		}
		
		public function blue(value:Number):void {
			value = cleanValue(value, 1)+1;
			if (value == 0 || isNaN(value)) { return; }
			multiplyMatrix([
				1, 0, 0, 0, 0,
				0, 1, 0, 0, 0,
				0, 0, value, 0, 0,
				0, 0, 0, 1, 0
			]);
		}
		
		public function alpha(value:Number):void {
			value = cleanValue(value, 1)+1;
			if (value == 0 || isNaN(value)) { return; }
			multiplyMatrix([
				1, 0, 0, 0, 0,
				0, 1, 0, 0, 0,
				0, 0, 1, 0, 0,
				0, 0, 0, value, 0
			]);
		}
		
		public function saturation(value:Number):void {
			value = cleanValue(value,100);
			if (value == 0 || isNaN(value)) { return; }
			value = ((value > 0)? 3 * value/100 : value/100) + 1;
			
			var lumR:Number = 0.212671;
			var lumG:Number = 0.715160;
			var lumB:Number = 0.072169;

			var sf:Number = value;
			var nf:Number = 1-sf;
			var nr:Number = lumR * nf;
			var ng:Number = lumG * nf;
			var nb:Number = lumB * nf;

			multiplyMatrix([
				nr+sf,	ng,		nb,		0,	0,
				nr,		ng+sf,	nb,		0,	0,
				nr,		ng,		nb+sf,	0,	0,
				0,  	0, 		0,  	1,  0
			]);
		}
		
		public function hue(value:Number):void {
			value = cleanValue(value,180)/180*Math.PI;
			if (value == 0 || isNaN(value)) { return; }
			var cosVal:Number = Math.cos(value);
			var sinVal:Number = Math.sin(value);
			var lumR:Number = 0.213;
			var lumG:Number = 0.715;
			var lumB:Number = 0.072;
			multiplyMatrix([
				lumR + cosVal * (1 - lumR) + sinVal * ( -lumR), lumG + cosVal * ( -lumG) + sinVal * ( -lumG), lumB + cosVal * ( -lumB) + sinVal * (1 - lumB), 0, 0,
				lumR + cosVal * ( -lumR) + sinVal * (0.143), lumG + cosVal * (1 - lumG) + sinVal * (0.140), lumB + cosVal * ( -lumB) + sinVal * ( -0.283), 0, 0,
				lumR + cosVal * ( -lumR) + sinVal * ( -(1 - lumR)), lumG + cosVal * ( -lumG) + sinVal * (lumG), lumB + cosVal * (1 - lumB) + sinVal * (lumB), 0, 0,
				0, 0, 0, 1, 0
			]);
		}
		
		public function brightness(value:Number):void {
			value = cleanValue(value, 100);
			if (value == 0 || isNaN(value)) { return; }
			multiplyMatrix([
				1, 0, 0, 0, value,
				0, 1, 0, 0, value,
				0, 0, 1, 0, value,
				0, 0, 0, 1, 0
			]);
		}
		
		public function contrast(value:Number):void {
			value = cleanValue(value, 100);
			if (value == 0 || isNaN(value)) { return; }
			var x:Number;
			if (value < 0) {
				x = 127 + value / 100 * 127
			} else {
				x = value % 1;
				if (x == 0) 
					x = DELTA_INDEX[value];
				else 
					x = DELTA_INDEX[(value << 0)] * (1 - x) + DELTA_INDEX[(value << 0) + 1] * x;
				x = x*127+127;
			}
			multiplyMatrix([
				x / 127, 0, 0, 0, 0.5 * (127 - x),
				0, x / 127, 0, 0, 0.5 * (127 - x),
				0, 0, x / 127, 0, 0.5 * (127 - x),
				0, 0, 0, 1, 0
			]);
		}
		
		public function clone():ColorMatrix {
			return new ColorMatrix(this);
		}
		
		public function toArray():Array {
			return slice(0,20);
		}
		
		private function copyMatrix(matrix:Array):void {
			for (var i:Number = 0; i < LENGTH; i++) {
				this[i] = matrix[i];
			}
		}
		
		private function multiplyMatrix(matrix:Array):void {
			var col:Array = [];
			for (var i:Number = 0; i < 4; i++) {
				for (var t:Number = 0; t < 5; t++) {
					col[t] = this[t + i * 5];
				}
				for (var j:Number = 0; j < 5; j++) {
					var val:Number=0;
					for (var k:Number = 0; k < 4; k++) {
						val += matrix[j + k * 5] * col[k];
					}
					this[j + i * 5] = val;
				}
			}
		}
		
		private function cleanValue(value:Number,limit:Number):Number {
			return Math.min(limit, Math.max( -limit, value));
		}
		
	}
	
}
